<?php
/**
 * Klasse zum Aufbau und Validierung von Formularen
 *
 * @author		Joachim Ruf
 * @copyright	Copyright (c) 2003-2013 Loresoft Software (http://www.loresoft.de)
 * @license		http://yapary.loresoft.de/lizenzbedingungen     New BSD License
 *
 * */


Class Yapary_Form_Formular# Extends Yapary_WWW_Html_Scanner
{
	private $showErrMsg		= false;
	private $formId			= NULL;
	private $method			= NULL;
	private $action			= NULL;
	private $enctype		= NULL;

	/**
	array(
			'element'			=>	string				# Elementname [select|input|textarea|...]
			'subject'			=>	string				# Benutzereingabe ohne HTML-Code mit Replace-Token {{REPLACE_ID}}
			'desc'				=>	$_desc,				# Elementbezeichnung
			'arrParam'			=>	array(),			# Parameterliste
			'arrMultiParam'		=>	array(),			# Nur fuer Select-Element array(value=>'value', 'arrOption'=>array)
			'validate'			=>	Yapary_Parameter_ICheck,	# Zu pruefende Validierung
			'valid'				=>	NULL|true|false,	# Angabe, nach der Validierung
			'required'			=>	true|false,			# Angabe, ob das Element erforderlich oder optional ist
			'err'				=>	string|NULL			# Fehlermeldung durch die Validierung
	);
	 * */
	private $arrList	= array();
	private $arrErr		= array();


	/**
	 * @param string $_method	: post|get
	 * @param string $_action	: aufzurufende Seite
	 * @param string $_enctype	: multipart/form-data
	 * */
	function __construct ($_formId = 'yapary_formular', $_method =  '', $_action = '', $_enctype = 'multipart/form-data') {
		$this->formId	= $_formId;
		$this->method	= $_method;
		$this->action	= $_action;
		$this->enctype	= $_enctype;
	}


	/**
	 * @desc Fuegt eine lerre Zeile zum befuellen ein.
	 * @param string $_data					: Einzufuegfende Daten
	 * @return boolean
	 * */
	public function addTemplateRow ($_data) {

		$this->arrList[] = array(
				'element'	=>	'template',
				'subject'	=>	$_data,
				'arrParam'	=>	array(),
				'desc'		=>	NULL,
				'validate'	=>	NULL,
				'valid'		=>	NULL,
				'required'	=>	false,
				'err'		=>	NULL
		);
	}


	/**
	 * @desc Fuegt ein neues Input-Element hinzu.
	 * @param string $_elementName			: Name des HTML-Elements
	 * @param string $_inputTag				: Element
	 * @param string $_desc					: Voranzustellende Beschreibung
	 * @param object $_parameterValidation	: Parameter-Klasse
	 * @param boolean $_required			: true|false
	 * @return boolean
	 * */
	private function inputHelper ($_elementName, $_inputTag, $_desc, Yapary_Parameter_ICheck $_parameterValidation, $_required = false) {
		$arrParam = $this->deserializeParam($_inputTag);

		if (!isset($arrParam['name'])) {
			return false;
		}

		$id = $arrParam['name'];
		$this->arrList[$id] = array(
				'element'	=>	$_elementName,
				'subject'	=>	preg_replace('/\<input([^\>]*)\>/ims', '{{REPLACE_ID}}', $_inputTag),
				'arrParam'	=>	$arrParam,
				'desc'		=>	$_desc,
				'validate'	=>	$_parameterValidation,
				'valid'		=>	NULL,
				'required'	=>	$_required,
				'err'		=>	NULL
		);

		return true;
	}


	/**
	 * @desc Fuegt ein neues Input-Element hinzu.
	 * @param string $_inputTag				: Element
	 * @param string $_desc					: Voranzustellende Beschreibung
	 * @param object $_parameterValidation	: Parameter-Klasse
	 * @param boolean $_required			: true|false
	 * @return boolean
	 * */
	public function addInput ($_inputTag, $_desc, Yapary_Parameter_ICheck $_parameterValidation, $_required = false) {
		return $this->inputHelper('input', $_inputTag, $_desc, $_parameterValidation, $_required);
	}


	/**
	 * @desc Fuegt ein neues Input-Element hinzu.
	 * @param string $_inputTag				: Element
	 * @param string $_desc					: Voranzustellende Beschreibung
	 * @param object $_parameterValidation	: Parameter-Klasse zur Validierung des Dateiformats
	 * @param boolean $_required			: true|false
	 * @return boolean
	 * */
	public function addFile ($_inputTag, $_desc, Yapary_Parameter_ICheck $_parameterValidation, $_required = false) {
		return $this->inputHelper('file', $_inputTag, $_desc, $_parameterValidation, $_required);
	}


	/**
	 * @desc Fuegt ein neues Checkbox-Element hinzu.
	 * @param string $_inputTag				: Element
	 * @param string $_desc					: Voranzustellende Beschreibung
	 * @param object $_parameterValidation	: Parameter-Klasse
	 * @param boolean $_required			: true|false
	 * @return boolean
	 * */
	public function addCheckbox ($_inputTag, $_desc, $_required = false) {
		return $this->inputHelper('checkbox', $_inputTag, $_desc, new Yapary_Parameter_CheckIsSet(), $_required);
	}


	/**
	 * @desc Fuegt ein neues Radio-Element hinzu.
	 * @param string $_inputTag				: Element
	 * @param string $_inputParameter		: <input>-Parameterliste im Format class=".." title=".."
	 * @param string $_desc					: Voranzustellende Beschreibung
	 * @param object $_parameterValidation	: Parameter-Klasse
	 * @param boolean $_required			: true|false
	 * @return boolean
	 * */
	public function addRadio ($_inputTag, $_desc, Yapary_Parameter_ICheck $_parameterValidation, $_required = false) {
		preg_match_all('/\<\s*input([^>]*)\>/ims', $_inputTag, $arrTmp);

		$name = '';
		if (count($arrTmp)) {
			foreach ($arrTmp[1] as $key => $val) {
				$arrTmp = $this->deserializeParam($val);
				if (!isset($arrTmp['name'])) {
					continue;
				}

				$arrMultiParam[$arrTmp['value']] = $arrTmp;
				$name	= $arrTmp['name'];
				$value	= $arrTmp['value'];
			}
		}

		$this->arrList[$value] = array(
			'element'			=>	'radio',
			'subject'			=>	preg_replace('/\<input([^\>]*)\>/ims', '{{REPLACE_ID}}', $_inputTag),
			'arrParam'			=>	array('name' => $name),
			'arrMultiParam'		=>	$arrMultiParam,
			'desc'				=>	$_desc,
			'validate'			=>	$_parameterValidation,
			'valid'				=>	NULL,
			'required'			=>	$_required,
			'err'				=>	NULL
		);

		return true;
	}


	/**
	 * @desc Fuegt ein neues Textarea-Element hinzu.
	 * @param string $_inputTag				: Element
	 * @param string $_desc					: Voranzustellende Beschreibung
	 * @param object $_parameterValidation	: Parameter-Klasse
	 * @param boolean $_required			: true|false
	 * @return boolean
	 * */
	public function addTextarea ($_inputTag, $_desc, Yapary_Parameter_ICheck $_parameterValidation, $_required = false) {
    	$arrParam = $this->deserializeParam($_inputTag);

    	if (!isset($arrParam['name'])) {
    		return false;
    	}
    	$id = $arrParam['name'];

    	$this->arrList[$id] = array(
    		'element'	=>	'textarea',
			'subject'	=>	preg_replace('/\<textarea(.*?)\/textarea\>/ims', '{{REPLACE_ID}}', $_inputTag),
    		'arrParam'	=>	$arrParam,
			'desc'		=>	$_desc,
			'validate'	=>	$_parameterValidation,
			'valid'		=>	NULL,
			'required'	=>	$_required,
    		'err'		=>	NULL
		);

    	return true;
	}


	/**
	 * @desc Fuegt ein neues Select-Element hinzu.
	 * @param string $_inputTag				: Element
	 * @param string $_inputParameter		: <input>-Parameterliste im Format class=".." title=".."
	 * @param string $_desc					: Voranzustellende Beschreibung
	 * @param object $_parameterValidation	: Parameter-Klasse
	 * @param boolean $_required			: true|false
	 * @return boolean
	 * */
	public function addSelect ($_inputTag, $_desc, Yapary_Parameter_ICheck $_parameterValidation, $_required = false) {
		preg_match('/\<\s*select([^>]*)\>/ims', $_inputTag, $arrSelectTmp);
		$arrParam = $this->deserializeParam($arrSelectTmp[1]);
		$arrMultiParam = $this->deserializeSelectParam($_inputTag);

		if (!isset($arrParam['name'])) {
			return false;
		}

		$id = $arrParam['name'];
		$this->arrList[$id] = array(
			'element'			=>	'select',
			'subject'			=>	preg_replace('/\<select(.*?)\/select\>/ims', '{{REPLACE_ID}}', $_inputTag),
			'arrParam'			=>	$arrParam,
			'arrMultiParam'		=>	$arrMultiParam,
			'desc'				=>	$_desc,
			'validate'			=>	$_parameterValidation,
			'valid'				=>	NULL,
			'required'			=>	$_required,
			'err'				=>	NULL
		);

		return true;
	}


	/**
	 * @desc Fuegt zu einem Element eine Fehlermeldung hinzu.
	 * @param string $_elName	: Name des Tags name=""
	 * @param string $_errMsg	: Fehlernachricht zu dem Fehler
	 * @param boolean
	 * */
	public function addExternalError ($_elName, $_errMsg) {
		if (!isset($this->arrList[$_elName])) {
			return false;
		}
		if ($this->arrList[$_elName]['err'] != NULL) { // Nur eintragen, wenn noch kein interner Fehler vorhanden ist
			return false;
		}

		$this->arrList[$_elName]['err'] = $_errMsg;
		$this->arrErr[$_elName] = $_errMsg;
		return true;
	}


	private function getTagByParamV ($paramName) {

	}

	private function setParam ($tag, $paramName, $paramValue = NULL) {

	}

	/**
	 * Hilfsklasse zur Ausgabe des Wertes eines Parameters
	 * */
	private function serializeParam($arrList) {
		$result = '';

		if (count($arrList)) {
			foreach ($arrList as $key => $value) {
				$result .= $key.'="'.$value.'" ';
			}
		}

		return $result;
	}


	/**
	 * Hilfsklasse zur Ausgabe des Wertes eines Parameters
	 * */
	private function deserializeParam($string) {
    	preg_match_all('/\s*([a-z]+)\s*=\s*\"([^\"]*)\"/ims', $string, $arrList);

    	if (count($arrList)) {
			foreach ($arrList[1] as $entry => $name) {
				$value = $arrList[2][$entry];
				$arrParam[$name] = $value;
			}
		}

		return $arrParam;
	}


	/**
	 * Hilfsklasse zur Ausgabe des Wertes eines Select-Elements
	 * */
	private function serializeSelectParam($arrList) {
		$result = '';
//		echo '<pre>'.print_r($arrList, true).'</pre>';

		if (count($arrList)) {
			foreach ($arrList as $list) {
				// echo '<pre>'.print_r($list, true).'</pre>';
				$result .= '<option '.$this->serializeParam($list['arrOption']).'>'.$list['name'].'</option>';
			}
		}

		return $result;
	}


	/**
	 * Hilfsklasse zur Ausgabe des Wertes eines Select-Elements
	 * */
	private function deserializeSelectParam($string) {
		preg_match_all('/\<\s*option([^\>]*)\>([^\<]{0,})\<\/option\>/ims', $string, $arrList);
//		echo '<pre>'.print_r($arrList, true).'</pre>';

		if (count($arrList)) {
			foreach ($arrList[1] as $entry => $name) {
				$value = $arrList[2][$entry];
//				$arrParam[$name] = $value;
//				echo "$name ==> $value<br />";

				$arrTmp = $this->deserializeParam($name);
				$id = $arrTmp['value'];
				$arrParam[$id] = array('name' => $value, 'arrOption' => $arrTmp);
			}
		}

		return $arrParam;
	}


	/**
	 * Liefert anhand der array-Liste das HTML-Element zurueck
	 * @param array $arrElement	: Array mit den HTML-Informationen
	 * @return string
	 * */
	private function getHtmlElement ($arrElement) {
		$param = $this->serializeParam($arrElement['arrParam']);
		switch ($arrElement['element']) {
			case 'template':
				return $arrElement['subject'];
				break;
			case 'input':
			case 'checkbox':
			case 'file':
				return str_replace('{{REPLACE_ID}}', '<input '.$param.'/>', $arrElement['subject']);
				break;
			case 'radio':
				if (isset($arrElement['arrMultiParam'])) {
					foreach ($arrElement['arrMultiParam'] as $key => $val) {
						//echo '<pre>'.print_r($val, true).'</pre>';
						$arrElement['subject'] = preg_replace('/{{REPLACE_ID}}/', '<input '.$this->serializeParam($val).' />', $arrElement['subject'], 1);
					}
				}
				return $arrElement['subject'];
				break;
			case 'textarea':
				if (isset($arrElement['arrParam']['value'])) {
					$value = $arrElement['arrParam']['value'];
				} else {
					$value = '';
				}
				return str_replace('{{REPLACE_ID}}', '<textarea '.$param.'>'.$value.'</textarea>', $arrElement['subject']);
				break;
			case 'select':
				if (isset($arrElement['arrMultiParam'])) {
					$value = $this->serializeSelectParam($arrElement['arrMultiParam']);
				} else {
					$value = '';
				}
				return str_replace('{{REPLACE_ID}}', '<select '.$param.'>'.$value.'</select>', $arrElement['subject']);
				break;
		}
	}

	/**
	 * Loescht einen Eintrag.
	 * @param string $id	: Zu loeschender Eintrag
	 * @return bool
	 * */
	public function delete ($id) {
		if (!isset($this->arrList[$id])) {
			return false;
		}

		unset($this->arrList[$id]);
		return true;
	}


	/**
	 * @desc Gibt an, ob das Formular bereits versendet wurde.
	 * @return bool
	 * */
	private function getValue ($key) {
		switch ($this->method) {
			case 'post':
				if (isset($_POST[$key])) {
					return $_POST[$key];
				}
				break;
			case 'get':
				if (isset($_GET[$key])) {
					return $_GET[$key];
				}
				break;
		}

		return NULL;
	}


	/**
	 * @desc Gibt an, ob das Formular bereits versendet wurde.
	 * @return bool
	 * */
	public function hasSend () {
		if ($this->getValue($this->formId) == NULL){
			return false;
		}

		return true;
	}


	/**
	 * @desc Validiert alle Formulareingaben.
	 * @return int|NULL	: Validierung erfolgreich=true, sonst false
	 * */
	public function validate () {
		$count = count($this->arrList);
		$isValid = true;

		if ($count < 1) {
			return NULL;
		}
		if (!$this->hasSend()) {
			return NULL;
		}

		//echo '<pre>'.print_r($this->arrList, true).'</pre>';
		foreach ($this->arrList as $key => $arr) {
			if ($arr['element'] == 'template') {
				continue;
			}
			$name		= $arr['arrParam']['name'];
			$required	= $arr['required'];
			$val		= $this->getValue($name);

			if (strlen($val) == 0 && $required == false) {
				continue;
			}
			//echo $name.'='.$val.'<br />';

			/* @var $oParameter Yapary_Parameter_ICheck */
			$oParameter = $arr['validate'];
			$this->arrList[$key]['valid'] = $oParameter->isValid($val);

			switch ($this->arrList[$key]['element']) {
				case 'file':
					// Validation Ergebnis ueberschreiben
					//echo $name;
					//echo print_r($_FILES[$name]);
					$this->arrList[$key]['valid'] = $oParameter->isValid($_FILES[$name]['type']);
					break;
				case 'checkbox':
					if ($oParameter->isValid($val)) {
						$this->arrList[$key]['arrParam']['checked'] = 'checked';
					}
					break;
				case 'radio':
					//echo "$name=$val<br />";
					if ($oParameter->isValid($val)) {
						//echo 'you did it';
						$this->arrList[$key]['arrMultiParam'][$val]['checked'] = 'checked';
					}
					break;
				default:
					$this->arrList[$key]['arrParam']['value'] = $oParameter->typedef($val);
			}


			if ($this->arrList[$key]['valid'] == false) {
				$isValid = false;
				$this->arrList[$key]['err'] = $oParameter->getErrMsg($oParameter->getLastError());
				$this->arrErr[$key] = $oParameter->getErrMsg($oParameter->getLastError());
			} else {
				if ($this->arrList[$key]['element'] == 'select') {
					$this->arrList[$key]['arrMultiParam'][$val]['arrOption']['selected']='selected';
				}
			}
		}

		return $isValid;
	}


	/**
	 * @desc Liefert alle entstandenen Fehler.
	 * @return array
	 * */
	public function getError () {
		return $this->arrErr;
	}


	/**
	 * @desc Liefert die Anzahl an internen und externen Fehlern.
	 * @return int
	 * */
	public function getErrorCount () {
		return (int) count($this->getError());
	}


	/**
	 * @desc Gibt an, dass Fehlercodes aus der Validierung angezeigt werden sollen
	 * @return void
	 * */
	public function showErrMsg () {
		$this->showErrMsg = true;
	}


	/**
	 * @desc Gibt das Formular aus.
	 * @return string
	 * */
	public function display() {
		$result		= '';
		$count		= count($this->arrList);

		if ($count < 1) {
			return NULL;
		}


		foreach ($this->arrList as $key => $arr) {
			switch ($arr['element']) {
				case 'template':
					$result .= '<div class="Yapary_Form_Formular_row">
									'.$arr['subject'].'
								</div>';
					break;
				default:
					$result .= $this->displayRow($arr);
			}
		}

		return '<form id="'.$this->formId.'" method="'.$this->method.'" action="'.$this->action.'" enctype="'.$this->enctype.'" class="Yapary_Form_Formular">
					'.$result.'
					<input type="hidden" name="'.$this->formId.'" value="1" />
    			</form>';
	}


	/**
	 * @desc Hilfsklasse zur Ausgabe des Formulars.
	 * @return string
	 * */
	private function displayRow($arr) {
		$formValid	= '';
		$errMsg		= '';

		if (gettype($arr['valid']) == 'boolean') {
			switch (gettype($arr['err'])) {
				case 'NULL':
					$formValid	= ' Yapary_Form_Formular_valid';
					$errMsg		= '';
					break;
				case 'string':
					$formValid	= ' Yapary_Form_Formular_invalid';
					if ($this->showErrMsg == true) {
						$errMsg		= '<br /><span class="Yapary_Form_Formular_Error">['.$arr['err'].']</span>';
					}
					break;
			}
		}

		return $this->getRow($arr['desc'], $this->getHtmlElement($arr), $formValid, $errMsg);
	}


	/**
	 * @desc Liefert eine Zeile
	 * @param string $formValid	: CSS-Code fuer valid/invalid
	 * @param string $desc		: Beschreibung
	 * @param string $html		: Html-Tag
	 * @param string $errMsg	: Fehlernachricht
	 * @return string
	 * */
	private function getRow ($desc, $html, $formValid = '', $errMsg = '') {
		return '<div class="Yapary_Form_Formular_row'.$formValid.'">
					<div class="Yapary_Form_Formular_row_desc">
						'.$desc.'
					</div>
		    		<div class="Yapary_Form_Formular_row_data">
					'.$html.$errMsg.'
		    		</div>
				</div>';
	}

}


?>